#import "../../QRCommon.h"
#import "FinderPattern.h"
#import "../../geom/Axis.h"
#import "../../geom/IntPointHelper.h"
#import "../../Exceptions.h"
#import "../QRCodeImageReader.h"

static int VersionInfoBit[] = { 
  0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, 0x0C762, 0x0D847, 0x0E60D, 
  0x0F928, 0x10B78, 0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, 0x168C9, 
  0x177EC, 0x18EC4, 0x191E1, 0x1AFAB, 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 
  0x1F250, 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B, 0x2542E, 0x26A64, 
  0x27541, 0x28C69 };
#define VIBLEN 34

@interface FinderPattern (Private)
-(FinderPattern*)initWithCenter: (IntPointVector*) center version: (int) version sincos: (IntVector*) sincos width: (IntVector*) width moduleSize: (int) moduleSize;
+(NSArray*)findLineAcross: (BoolMatrix*) image;
+(NSArray*)findLineCross: (NSArray*) lineAcross;
+(BOOL)checkPattern: (int*) buffer pointer: (int) pointer;
+(BOOL)cantNeighbor: (QRLine*) line1 line2: (QRLine*) line2;
+(IntVector*)getAngle: (IntPointVector*) centers;
+(IntPointVector*)getCenter: (NSArray*) crossLines;
+(IntPointVector*)sort: (IntPointVector*) centers angle: (IntVector*)angle;
+(int)getURQuadant: (IntVector*) angle;
+(IntPoint)getPointAtSide: (IntPointVector*) points side1: (int) side1 side2: (int) side2;
+(IntVector*)getWidth: (BoolMatrix*) image centers: (IntPointVector*) centers sincos: (IntVector*) sincos;
+(int)calcRoughVersion: (IntPointVector*) center width: (IntVector*) width;
+(int)calcExactVersion: (IntPointVector*) center angle: (IntVector*) angle moduleSize: (const int*) moduleSize image: (BoolMatrix*) image;
+(int)checkVersionInfo: (BOOL*) target;
@end

@implementation FinderPattern
+(FinderPattern*)findFinderPattern: (BoolMatrix*) image
{
  NSArray *lineAcross = [self findLineAcross: image];
  NSArray *lineCross = [self findLineCross: lineAcross];

  IntPointVector *center = nil;
  @try
    {
      center = [self getCenter: lineCross];
    }
  @catch (FinderPatternNotFoundException *e)
    {
      @throw;
    }

  IntVector* sincos = [self getAngle: center];
  center = [self sort: center angle: sincos];
  IntVector *width = [self getWidth: image centers: center sincos: sincos];

  // moduleSize for version recognition
  int _moduleSize[2];
  int dp = [QRCodeImageReader DECIMAL_POINT];
  _moduleSize[0] = ([width get:FP_UR] << dp) / 7;
  _moduleSize[1] = ([width get:FP_DL] << dp) / 7;

  int version = [self calcRoughVersion: center width: width];
  NSLog(@"Rough version: %d", version);
  if (version > 6)
    {
      @try
	{
	  version = [self calcExactVersion: center angle: sincos moduleSize: _moduleSize image: image];
	}
      @catch (VersionInformationException *e)
	{
	  NSLog(@"Exact version failed, using rough version.");
	  //use rough version data
	  // throw e;
	}
    }
  return [[[FinderPattern alloc] initWithCenter: center version: version sincos: sincos width: width moduleSize: _moduleSize[0]] autorelease];
}

-(int)Version
{
  return version;
}

-(int)SqrtNumModules
{
  return 17 + 4 * version;
}

-(IntPointVector*)getCenter
{
  return center;
}

-(IntPoint)getCenter: (int) position
{
  if (position >= FP_UL && position <= FP_DL)
    {
      return [center get: position];
    }
  else
    {
      IntPoint p = { 0, 0 };
      return p;
    }
}

-(int)getWidth: (int) position
{
  return [width get: position];
}

-(IntVector*)getAngle
{
  return sincos;
}

-(int)getModuleSize
{
  return moduleSize;
}

-(FinderPattern*)initWithCenter: (IntPointVector*) c version: (int) v sincos: (IntVector*) sc width: (IntVector*) w moduleSize: (int) ms
{
  [super init];
  center = [c retain];
  version = v;
  sincos = [sc retain];
  width = [w retain];
  moduleSize = ms;
  return self;
}

-(void)dealloc
{
  [center release];
  [sincos release];
  [width release];
  [super dealloc];
}

+(NSArray*)findLineAcross: (BoolMatrix*) image
{
  int READ_HORIZONTAL = 0;
  int READ_VERTICAL = 1;
  
  int imageWidth = [image width];
  int imageHeight = [image height];
			
  IntPoint current = { 0, 0 };
  NSMutableArray* lineAcross = [[NSMutableArray alloc] init];
  
  //buffer contains recent length of modules which has same brightness
  int lengthBuffer[5];
  int bufferPointer = 0;
			
  int direction = READ_HORIZONTAL; //start to read horizontally
  BOOL lastElement = POINT_LIGHT;
			
  while (true)
    {
      //check points in image
      BOOL currentElement = [image X: current.x Y: current.y];
      if (currentElement == lastElement)
	{
	  //target point has same brightness with last point
	  lengthBuffer[bufferPointer]++;
	}
      else
	{
	  //target point has different brightness with last point
	  if (currentElement == POINT_LIGHT && [self checkPattern: lengthBuffer pointer: bufferPointer])
	    {
	      //detected pattern
	      int x1, y1, x2, y2, j;
	      if (direction == READ_HORIZONTAL)
		{
		  //obtain X coordinates of both side of the detected horizontal pattern
		  x1 = current.x;
		  for (j = 0; j < 5; j++)
		    {
		      x1 -= lengthBuffer[j];
		    }
		  x2 = current.x - 1; //right side is last X coordinate
		  y1 = y2 = current.y;
		}
	      else
		{
		  x1 = x2 = current.x;
		  //obtain Y coordinates of both side of the detected vertical pattern
		  // upper side is sum of length of buffer
		  y1 = current.y;
		  for (j = 0; j < 5; j++)
		    {
		      y1 -= lengthBuffer[j];
		    }
		  y2 = current.y - 1; // bottom side is last Y coordinate
		}
	      QRLine* nln = [[QRLine alloc] initWithX1: x1 Y1: y1 X2: x2 Y2: y2];
	      [lineAcross addObject: nln];
	      [nln release];
	    }
	  bufferPointer = (bufferPointer + 1) % 5;
	  lengthBuffer[bufferPointer] = 1;
	  lastElement = !lastElement;
	}
      
      // determine if read next, change read direction or terminate this loop
      if (direction == READ_HORIZONTAL)
	{
	  if (current.x < imageWidth - 1)
	    {
	      current.x++;
	    }
	  else if (current.y < imageHeight - 1)
	    {
	      current.x = 0;
	      current.y ++;
	      memset(lengthBuffer, 0, 5*sizeof(int));
	    }
	  else
	    {
	      current.x = 0;
	      current.y = 0;
	      memset(lengthBuffer, 0, 5*sizeof(int));
	      direction = READ_VERTICAL; //start to read vertically
	    }
	  continue;
	}
      
      //reading vertically
      if (current.y < imageHeight - 1)
	{
	  current.y++;
	  continue;
	}
      if (current.x >= imageWidth - 1)
	break; 
      current.x ++;
      current.y = 0;
      memset(lengthBuffer, 0, 5*sizeof(int));
    }

  // canvas.drawLines(foundLines, ThoughtWorks.QRCode.Codec.Util.Color_Fields.LIGHTGREEN);
  return [lineAcross autorelease];
}

+(NSArray*)findLineCross: (NSArray*) lineAcross
{
  NSMutableArray* crossLines = [[NSMutableArray alloc] init];
  NSMutableArray* lineNeighbor = [[NSMutableArray alloc] init];
  NSMutableArray* lineCandidate = [[NSMutableArray alloc] init];

  QRLine *compareLine;
  int i, j, k;
  for (i = 0; i < [lineAcross count]; i++)
    {
      [lineCandidate addObject: [lineAcross objectAtIndex: i]];
    }

  for (i = 0; i < [lineCandidate count] - 1; i++)
    {
      [lineNeighbor removeAllObjects];
      [lineNeighbor addObject: [lineCandidate objectAtIndex: i]];

      for (j = i + 1; j < [lineCandidate count]; j++)
	{
	  QRLine* l1 = [lineNeighbor objectAtIndex: [lineNeighbor count] - 1];
	  if ([l1 isNeighbor: [lineCandidate objectAtIndex: j]])
	    {
	      [lineNeighbor addObject: [lineCandidate objectAtIndex: j]];
	      compareLine = [lineNeighbor objectAtIndex: [lineNeighbor count] - 1];

	      if ([lineNeighbor count] * 5 > [compareLine Length] && j == [lineCandidate count] - 1)
		{
		  [crossLines addObject: [lineNeighbor objectAtIndex: [lineNeighbor count] / 2]];
		  for (k = 0; k < [lineNeighbor count]; k++)
		    {
		      [lineCandidate removeObject: [lineNeighbor objectAtIndex:k]];
		    }
		}
	    }	 
	  //terminate comparison if there are no possibility for found neighbour lines
	  else if ([self cantNeighbor: [lineNeighbor objectAtIndex: [lineNeighbor count] - 1] line2: [lineCandidate objectAtIndex: j]] || 
		   (j == [lineCandidate count] - 1))
	    {
	      compareLine = [lineNeighbor objectAtIndex: [lineNeighbor count] - 1];
	      /*
	       * determine lines across Finder Patterns when number of neighbour lines are 
	       * bigger than 1/6 length of theirselves
	       */
	      if ([lineNeighbor count] * 6 > [compareLine Length])
		{
		  [crossLines addObject: [lineNeighbor objectAtIndex: [lineNeighbor count] / 2]];
		  for (k = 0; k < [lineNeighbor count]; k++)
		    {
		      [lineCandidate removeObject: [lineNeighbor objectAtIndex: k]];
		    }
		}
	      break;
	    }
	}
    }
  [lineNeighbor release];
  [lineCandidate release];
  /*
  int jk;
  for (jk = 0; jk < [crossLines count]; jk++)
    {
      QRLine *qr = [crossLines objectAtIndex: jk];
      NSLog(@"Line %d: (%d, %d) (%d, %d)", jk, [qr getP1].x, [qr getP1].y, [qr getP2].x, [qr getP2].y);
    }
  */
  return [crossLines autorelease];
}

+(BOOL)checkPattern: (int*) buffer pointer: (int) pointer
{
  int modelRatio[5] = {1, 1, 3, 1, 1};
			
  int baselength = 0, i;
  for (i = 0; i < 5; i++)
    {
      baselength += buffer[i];
    }
  // pseudo fixed point calculation. I think it needs smarter code
  baselength <<= [QRCodeImageReader DECIMAL_POINT];
  baselength /= 7;

  for (i = 0; i < 5; i++)
    {
      int leastlength = baselength * modelRatio[i] - baselength / 2;
      int mostlength = baselength * modelRatio[i] + baselength / 2;
      
      leastlength -= baselength/8;
      mostlength += baselength/8;
      
      int targetlength = buffer[(pointer + i + 1) % 5] << [QRCodeImageReader DECIMAL_POINT];
      if (targetlength < leastlength || targetlength > mostlength)
	{
	  return NO;
	}
    }
  return YES;
}

+(BOOL)cantNeighbor: (QRLine*) line1 line2: (QRLine*) line2
{
  if ([line1 isCross: line2])
    {
      return YES;
    }
  
  if ([line1 Horizontal])
    {
      if (abs([line1 getP1].y - [line2 getP1].y) > 1)
	return YES;
      else
	return NO;
    }
  else
    {
      if (abs([line1 getP1].x - [line2 getP1].x) > 1)
	return YES;
      else
	return NO;
    }
}

+(IntVector*)getAngle: (IntPointVector*) centers
{
  QRLine* additionalLine[3];
  int alLen = 3, i;
			
  for (i = 0; i < alLen; i++)
    {
      additionalLine[i] = [[QRLine alloc] initWithP1: [centers get:i] P2: [centers get: ((i + 1) % alLen)]];
    }

  // remoteLine - does not contain UL center
  QRLine *remoteLine = [QRLine getLongest: additionalLine count: alLen];
  IntPoint originPoint = { 0, 0 };
  for (i = 0; i < [centers length]; i++)
    {
      if (![IntPointHelper equals: [remoteLine getP1] p2: [centers get:i]] && 
	  ![IntPointHelper equals: [remoteLine getP2] p2: [centers get:i]])
	{
	  originPoint = [centers get:i];
	  break;
	}
    }
	
  //canvas.println("originPoint is: " + originPoint);
  IntPoint remotePoint = { 0, 0 };
			
  //with origin that the center of Left-Up Finder Pattern, determine other two patterns center.
  //then calculate symbols angle
  if ((originPoint.y <= [remoteLine getP1].y) & (originPoint.y <= [remoteLine getP2].y))
    {
      if ([remoteLine getP1].x < [remoteLine getP2].x)
	{
	  remotePoint = [remoteLine getP2];
	}
      else
	{
	  remotePoint = [remoteLine getP1];
	}
    }
  else if ((originPoint.x >= [remoteLine getP1].x) & (originPoint.x >= [remoteLine getP2].x))
    {
      if ([remoteLine getP1].y < [remoteLine getP2].y)
	{
	  remotePoint = [remoteLine getP2];
	}
      else
	{
	  remotePoint = [remoteLine getP1];
	}
    }
  else if ((originPoint.y >= [remoteLine getP1].y) & (originPoint.y >= [remoteLine getP2].y))
    {
      if ([remoteLine getP1].x < [remoteLine getP2].x)
	{
	  remotePoint = [remoteLine getP1];
	}
      else
	{
	  remotePoint = [remoteLine getP2];
	}
    }
  //1st or 4th quadrant
  else if ([remoteLine getP1].y < [remoteLine getP2].y)
    {
      remotePoint = [remoteLine getP1];
    }
  else
    {
      remotePoint = [remoteLine getP2];
    }
  
  int r = [QRLine  getLength: originPoint p2: remotePoint];

  //canvas.println(Integer.toString(((remotePoint.getX() - originPoint.getX()) << QRCodeImageReader.DECIMAL_POINT)));
  IntVector *angle = [[IntVector alloc] initWithLength: 2];
  int dp = [QRCodeImageReader DECIMAL_POINT];
  [angle setValue: ((remotePoint.y - originPoint.y) << dp) / r at: 0]; //Sin
  [angle setValue: ((remotePoint.x - originPoint.x) << dp) / r at: 1]; //Cos
			
  return [angle autorelease];
}

+(IntPointVector*)getCenter: (NSArray*) crossLines
{
  IntPointVector* centers = nil;

  int cl = [crossLines count], i, j;
  for (i = 0; i < cl - 1; i++)
    {
      QRLine *compareLine = [crossLines objectAtIndex: i];
      for (j = i + 1; j < cl; j++)
	{
	  QRLine *comparedLine = [crossLines objectAtIndex: j];
	  if ([compareLine isCross: comparedLine])
	    {
	      int x = 0;
	      int y = 0;
	      if ([compareLine Horizontal])
		{
		  x = [compareLine Center].x;
		  y = [comparedLine Center].y;
		}
	      else
		{
		  x = [comparedLine Center].x;
		  y = [compareLine Center].y;
		}
	      
	      IntPoint np = { x, y };
	      if (centers == nil)
		{
		  centers = [[IntPointVector alloc] initWithLength: 1];
		  [centers setValue: np at: 0];
		}
	      else
		{
		  [centers add: np];
		}
	    }
	}
    }
			
  if (!centers || [centers length] != 3)
    {
      FinderPatternNotFoundException *ex = [FinderPatternNotFoundException withMessage: @"Invalid number of Finder Pattern detected"];
      @throw ex;
    }

  int jk;
  for (jk = 0; jk < [centers length]; jk++)
    {
      IntPoint qr = [centers get: jk];
      NSLog(@"Center %d: (%d, %d)", jk, qr.x,qr.y);
    }

  return [centers autorelease];
}

+(IntPointVector*)sort: (IntPointVector*) centers angle: (IntVector*)angle
{
  IntPointVector* sortedCenters = [[IntPointVector alloc] initWithLength: 3];
			
  int quadant = [self getURQuadant: angle];
  switch (quadant)
    {
      
    case 1: 
      [sortedCenters setValue: [self getPointAtSide: centers side1: IPRIGHT side2: IPBOTTOM] at: 1];
      [sortedCenters setValue: [self getPointAtSide: centers side1: IPBOTTOM side2: IPLEFT] at: 2];
      break;
      
    case 2: 
      [sortedCenters setValue: [self getPointAtSide: centers side1: IPBOTTOM side2: IPLEFT] at: 1];
      [sortedCenters setValue: [self getPointAtSide: centers side1: IPTOP side2: IPLEFT] at: 2];
      break;
      
    case 3: 
      [sortedCenters setValue: [self getPointAtSide: centers side1: IPLEFT side2: IPTOP] at: 1];
      [sortedCenters setValue: [self getPointAtSide: centers side1: IPRIGHT side2: IPTOP] at: 2];
      break;
      
    case 4: 
      [sortedCenters setValue: [self getPointAtSide: centers side1: IPTOP side2: IPRIGHT] at: 1];
      [sortedCenters setValue: [self getPointAtSide: centers side1: IPBOTTOM side2: IPRIGHT] at: 2];
      break;
    }
  
  //last of centers is Left-Up patterns one
  int i;
  for (i = 0; i < [centers length]; i++)
    {
      if (![IntPointHelper equals: [centers get: i] p2: [sortedCenters get: 1]] && 
	  ![IntPointHelper equals: [centers get: i] p2: [sortedCenters get: 2]])
	{
	  [sortedCenters setValue: [centers get: i] at: 0];
	}
    }
  
  return [sortedCenters autorelease];
}

+(int)getURQuadant: (IntVector*) angle
{
  int sin = [angle get: 0];
  int cos = [angle get: 1];

  if (sin >= 0 && cos > 0)
    return 1;
  else if (sin > 0 && cos <= 0)
    return 2;
  else if (sin <= 0 && cos < 0)
    return 3;
  else if (sin < 0 && cos >= 0)
    return 4;
  
  return 0;
}

+(IntPoint)getPointAtSide: (IntPointVector*) points side1: (int) side1 side2: (int) side2
{
  int x = ((side1 == IPRIGHT || side2 == IPRIGHT)?0:0x7FFFFFFF);
  int y = ((side1 == IPBOTTOM || side2 == IPBOTTOM)?0:0x7FFFFFFF);
  IntPoint sidePoint = { x, y };
  int i, len = [points length];
  
  for (i = 0; i < len; i++)
    {
      switch (side1)
	{
	  
	case IPRIGHT: 
	  if (sidePoint.x < [points get: i].x)
	    {
	      sidePoint = [points get: i];
	    }
	  else if (sidePoint.x == [points get: i].x)
	    {
	      if (side2 == IPBOTTOM)
		{
		  if (sidePoint.y < [points get: i].y)
		    {
		      sidePoint = [points get: i];
		    }
		}
	      else
		{
		  if (sidePoint.y > [points get: i].y)
		    {
		      sidePoint = [points get: i];
		    }
		}
	    }
	  break;
	  
	case IPBOTTOM: 
	  if (sidePoint.y < [points get: i].y)
	    {
	      sidePoint = [points get: i];
	    }
	  else if (sidePoint.y == [points get: i].y)
	    {
	      if (side2 == IPRIGHT)
		{
		  if (sidePoint.x < [points get: i].x)
		    {
		      sidePoint = [points get: i];
		    }
		}
	      else
		{
		  if (sidePoint.x > [points get: i].x)
		    {
		      sidePoint = [points get: i];
		    }
		}
	    }
	  break;
	  
	case IPLEFT: 
	  if (sidePoint.x > [points get: i].x)
	    {
	      sidePoint = [points get: i];
	    }
	  else if (sidePoint.x == [points get: i].x)
	    {
	      if (side2 == IPBOTTOM)
		{
		  if (sidePoint.y < [points get: i].y)
		    {
		      sidePoint = [points get: i];
		    }
		}
	      else
		{
		  if (sidePoint.y > [points get: i].y)
		    {
		      sidePoint = [points get: i];
		    }
		}
	    }
	  break;
	  
	case IPTOP: 
	  if (sidePoint.y > [points get: i].y)
	    {
	      sidePoint = [points get: i];
	    }
	  else if (sidePoint.y == [points get: i].y)
	    {
	      if (side2 == IPRIGHT)
		{
		  if (sidePoint.x < [points get: i].x)
		    {
		      sidePoint = [points get: i];
		    }
		}
	      else
		{
		  if (sidePoint.x > [points get: i].x)
		    {
		      sidePoint = [points get: i];
		    }
		}
	    }
	  break;
	}
    }
  return sidePoint;
}

+(IntVector*)getWidth: (BoolMatrix*) image centers: (IntPointVector*) centers sincos: (IntVector*) sincos
{
  IntVector *width = [[IntVector alloc] initWithLength: 3];
  int i;
  
  for (i = 0; i < 3; i++)
    {
      BOOL flag = NO;
      int lx, rx;
      int y = [centers get:i].y;

      for (lx = [centers get:i].x; lx > 0; lx--)
	{
	  if ([image X:lx Y:y] == POINT_DARK && [image X:(lx - 1) Y:y] == POINT_LIGHT)
	    {
	      if (flag == NO)
		{
		  flag = YES;
		}
	      else
		{
		  break;
		}
	    }
	}
      flag = NO;
      for (rx = [centers get:i].x; rx < [image width]; rx++)
	{
	  if ([image X:rx Y:y] == POINT_DARK && [image X:(rx + 1) Y:y] == POINT_LIGHT)
	    {
	      if (flag == NO)
		{
		  flag = YES;
		}
	      else
		{
		  break;
		}
	    }
	}
      [width setValue: (rx - lx + 1) at: i];
    }
  return [width autorelease];
}

+(int)calcRoughVersion: (IntPointVector*) center width: (IntVector*) width
{
  int dp = [QRCodeImageReader DECIMAL_POINT];
  int lengthAdditionalLine = [QRLine getLength: [center get: FP_UL] p2: [center get: FP_UR]] << dp;
  
  int averageWidth = (([width get: FP_UL] + [width get: FP_UR]) << dp) / 14;
  int roughVersion = ((lengthAdditionalLine / averageWidth) - 10) / 4;
  if (((lengthAdditionalLine / averageWidth) - 10) % 4 >= 2)
    {
      roughVersion++;
    }
  
  return roughVersion;
}

+(int)calcExactVersion: (IntPointVector*) center angle: (IntVector*) angle moduleSize: (const int*) moduleSize image: (BoolMatrix*) image
{
  BOOL versionInformation[18];
  IntPoint points[18];

  memset(versionInformation, 0, 18*sizeof(BOOL));
  memset(points, 0, sizeof(IntPoint)*18);

  IntPoint target;
  Axis *axis = [[Axis alloc] initWithAngle: [angle asROArray] pitch: moduleSize[0]];
  [axis setOrigin: [center get:FP_UR]];
			
  int x, y;
  for (y = 0; y < 6; y++)
    {
      for (x = 0; x < 3; x++)
	{
	  target = [axis translateX: (x - 7) Y: (y - 3)];
	  versionInformation[x + y * 3] = [image X: target.x Y: target.y];
	  points[x + y * 3] = target;
	}
    }
  //canvas.drawPoints(points, ThoughtWorks.QRCode.Codec.Util.Color_Fields.RED);
  
    int exactVersion = 0;
    @try
    {
      exactVersion = [self checkVersionInfo: versionInformation];
    }
    @catch (InvalidVersionInfoException *e)
    {
      //canvas.println("Version info error. now retry with other place one.");
      NSLog(@"Version info error. now retry with other place one.");
      [axis setOrigin: [center get: FP_DL]];
      [axis setModulePitch: moduleSize[1]];
      
      for (x = 0; x < 6; x++)
	{
	  for (y = 0; y < 3; y++)
	    {
	      target = [axis translateX: (x - 3) Y: (y - 7)];
	      versionInformation[y + x * 3] = [image X: target.x Y:target.y];
	      points[x + y * 3] = target;
	    }
	}
      //canvas.drawPoints(points, ThoughtWorks.QRCode.Codec.Util.Color_Fields.RED);
      
	@try
	{
	    exactVersion = [self checkVersionInfo: versionInformation];
	}
	@catch (VersionInformationException *e2)
	{
	    @throw;
	}
    }
    return exactVersion;
}

+(int)checkVersionInfo: (BOOL*) target
{
  // note that this method includes BCH 18-6 Error Correction
  // see page 67 on JIS-X-0510(2004) 
  int errorCount = 0, versionBase, j;
  for (versionBase = 0; versionBase < VIBLEN; versionBase++)
    {
      errorCount = 0;
      for (j = 0; j < 18; j++)
	{
	  // WAS THIS:
	  //if (target[j] ^ (VersionInfoBit[versionBase] >> j) % 2 == 1)
	  BOOL mybit = ((VersionInfoBit[versionBase] >> j)%2) == 1 ? YES : NO;
	  if (!(target[j] == mybit))
	    {
	      errorCount++;
	    }
	}
      if (errorCount <= 3)
	{
	  break;
	}
    }
  if (errorCount <= 3)
    {
      return 7 + versionBase;
    }
  else
    {
      NSException *e = [InvalidVersionInfoException withMessage: @"Too many errors in version information"];
      @throw e;
    }
}

RETAIN_RELEASE(FinderPattern)
@end
